Completed
Branch v10.2.x (c81ed9)
by Rafael S.
16:21
created

Interpolator.getTangent_   A

Complexity

Conditions 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
1
/*
2
 * Copyright (c) 2019 Rafael da Silva Rocha.
3
 * Copyright 2012 Spencer Cohen
4
 *
5
 * Permission is hereby granted, free of charge, to any person obtaining
6
 * a copy of this software and associated documentation files (the
7
 * "Software"), to deal in the Software without restriction, including
8
 * without limitation the rights to use, copy, modify, merge, publish,
9
 * distribute, sublicense, and/or sell copies of the Software, and to
10
 * permit persons to whom the Software is furnished to do so, subject to
11
 * the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be
14
 * included in all copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
 *
24
 */
25
26
/**
27
 * @fileoverview The Interpolator class. Based on Smooth.js by Spencer Cohen.
28
 * @see https://github.com/rochars/wavefile
29
 * @see https://github.com/osuushi/Smooth.js
30
 */
31
32
/**
33
 * A class to get scaled values out of arrays.
34
 * @extends WaveFileReader
35
 */
36
export class Interpolator {
37
  
38
  /**
39
   * @param {number} scaleFrom the length of the original array.
40
   * @param {number} scaleTo The length of the new array.
41
   * @param {?Object} details The extra configuration, if needed.
42
   */
43
  constructor(scaleFrom, scaleTo, details) {
44
    /**
45
     * The length of the original array.
46
     * @type {number}
47
     */
48
    this.length_ = scaleFrom;
49
    /**
50
     * The scaling factor.
51
     * @type {number}
52
     */
53
    this.scaleFactor_ = (scaleFrom - 1) / scaleTo;
54
    /**
55
     * The interpolation function.
56
     * @type {Function}
57
     */
58
    this.interpolate = this.sinc;
59
    if (details.method === 'point') {
60
    	this.interpolate = this.point;
61
    } else if(details.method === 'linear') {
62
    	this.interpolate = this.linear;
63
    } else if(details.method === 'cubic') {
64
    	this.interpolate = this.cubic;
65
    }
66
    /**
67
     * The clipping function.
68
     * @type {Function}
69
     */
70
    this.clip_ = clipClamp_;
71
    // The clip function
72
    if (details.clip === 'periodic') {
73
      this.scaleFactor_ = scaleFrom / scaleTo;
74
      this.clip_ = clipPeriodic_;
75
    } else if (details.clip === 'mirror') {
76
      this.clip_ = clipMirror_;
77
    }
78
    /**
79
     * The tanget factor for cubic interpolation.
80
     * @type {number}
81
     */
82
    this.tangentFactor_ = 1 - Math.max(0, Math.min(1, details.tension || 0));
83
    // Configure the kernel for sinc
84
    /**
85
     * The sinc filter size.
86
     * @type {number}
87
     */
88
    this.sincFilterSize_ = details.sincFilterSize || 1;
89
    /**
90
     * The sinc kernel.
91
     * @type {Function}
92
     */
93
    this.kernel_ = sincKernel_(details.sincWindow || gaussianWindow_);
94
    if (details.method === 'lanczos') {
95
      this.kernel_ = sincKernel_(lanczosWindow_(details.lanczosFilterSize));
96
      this.sincFilterSize = details.lanczosFilterSize;
97
    }
98
  }
99
100
  /**
101
   * @param {number} t The index to interpolate.
102
   * @param {Array|TypedArray} samples the original array.
103
   * @return {number} The interpolated value.
104
   */
105
  point(t, samples) {
106
    return this.getClippedInput_(Math.round(this.scaleFactor_ * t), samples);
107
  }
108
109
  /**
110
   * @param {number} t The index to interpolate.
111
   * @param {Array|TypedArray} samples the original array.
112
   * @return {number} The interpolated value.
113
   */
114
  linear(t, samples) {
115
    t = this.scaleFactor_ * t;
116
    let k = Math.floor(t);
117
    t -= k;
118
    return (1 - t) *
119
    	this.getClippedInput_(k, samples) + t *
120
    	this.getClippedInput_(k + 1, samples);
121
  }
122
123
  /**
124
   * @param {number} t The index to interpolate.
125
   * @param {Array|TypedArray} samples the original array.
126
   * @return {number} The interpolated value.
127
   */
128
  cubic(t, samples) {
129
    t = this.scaleFactor_ * t;
130
    let k = Math.floor(t);
131
    let m = [this.getTangent_(k, samples), this.getTangent_(k + 1, samples)];
132
    let p = [this.getClippedInput_(k, samples),
133
      this.getClippedInput_(k + 1, samples)];
134
    t -= k;
135
    let t2 = t * t;
136
    let t3 = t * t2;
137
    return (2 * t3 - 3 * t2 + 1) *
138
      p[0] + (t3 - 2 * t2 + t) *
139
      m[0] + (-2 * t3 + 3 * t2) *
140
      p[1] + (t3 - t2) * m[1];
141
  }
142
143
  /**
144
   * @param {number} t The index to interpolate.
145
   * @param {Array|TypedArray} samples the original array.
146
   * @return {number} The interpolated value.
147
   */
148
  sinc(t, samples) {
149
    t = this.scaleFactor_ * t;
150
    let k = Math.floor(t);
151
    let ref = k - this.sincFilterSize_ + 1;
152
    let ref1 = k + this.sincFilterSize_;
153
    let sum = 0;
154
    for (let n = ref, j = ref;
155
        ref <= ref1 ? j <= ref1 : j >= ref1; 
156
        n = ref <= ref1 ? ++j : --j) {
157
      sum += this.kernel_(t - n) * this.getClippedInput_(n, samples);
158
    }
159
    return sum;
160
  }
161
162
  /**
163
   * @param {number} k The scaled index to interpolate.
164
   * @param {Array|TypedArray} samples the original array.
165
   * @return {number} The tangent.
166
   * @private
167
   */
168
  getTangent_(k, samples) {
169
    return this.tangentFactor_ *
170
      (this.getClippedInput_(k + 1, samples) -
171
        this.getClippedInput_(k - 1, samples)) / 2;
172
  }
173
174
  /**
175
   * @param {number} t The scaled index to interpolate.
176
   * @param {Array|TypedArray} samples the original array.
177
   * @return {number} The interpolated value.
178
   * @private
179
   */
180
  getClippedInput_(t, samples) {
181
    if ((0 <= t && t < this.length_)) {
182
      return samples[t];
183
    } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
184
      return samples[this.clip_(t, this.length_)];
185
    }
186
  }
187
}
188
189
// Sinc functions
190
191
/**
192
 * The default window function.
193
 * @param {number} x The sinc signal.
194
 * @return {number}
195
 * @private
196
 */
197
function gaussianWindow_(x) {
198
  return Math.exp(-x * x);
199
}
200
201
/**
202
 * @param {Function} window The window function.
203
 * @return {Function}
204
 * @private
205
 */
206
function sincKernel_(window) {
207
  return function(x) { return sinc_(x) * window(x); };
208
}
209
210
/**
211
 * @param {number} size The filter size.
212
 * @return {Function}
213
 * @private
214
 */
215
function lanczosWindow_(size) {
216
  return function(x) { return sinc_(x / size); };
217
}
218
219
/**
220
 * @param {number} x The sinc signal.
221
 * @return {number}
222
 * @private
223
 */
224
function sinc_(x) {
225
  if (x === 0) {
226
    return 1;
227
  } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
228
    return Math.sin(Math.PI * x) / (Math.PI * x);
229
  }
230
}
231
232
// Clip functions
233
234
/**
235
 * @param {number} t The scaled index
236
 * @param {number} n The size of the original array
237
 * @return {number}
238
 * @private
239
 */
240
function clipClamp_(t, n) {
241
  return Math.max(0, Math.min(t, n - 1));
242
}
243
244
/**
245
 * @param {number} t The scaled index
246
 * @param {number} n The size of the original array
247
 * @return {number}
248
 * @private
249
 */
250
function clipPeriodic_(t, n) {
251
  t = t % n;
252
  if (t < 0) {
253
    t += n;
254
  }
255
  return t;
256
}
257
258
/**
259
 * @param {number} t The scaled index
260
 * @param {number} n The size of the original array
261
 * @return {number}
262
 * @private
263
 */
264
function clipMirror_(t, n) {
265
  let period = 2 * (n - 1);
266
  t = clipPeriodic_(t, period);
267
  if (t > n - 1) {
268
    t = period - t;
269
  }
270
  return t;
271
}
272